﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using QuailDashboard.View.Models;
using QuailDashboard.View.QuailServiceReference;

namespace QuailDashboard.View.Data
{
    public class RunStatusResultWrapper : Model
    {
        /// <summary>
        /// sync root used for multi-thread locks, I'm not sure this is necessary with silverlight
        /// </summary>
        private static readonly object SyncRoot = new object();

        /// <summary>
        /// a static result used as a fake result entry
        /// </summary>
        private static readonly GetRunStatusResult FakeResult = new GetRunStatusResult();

        /// <summary>
        /// list of all public properties except those starting with Is
        /// </summary>
        private static PropertyInfo[] _allPublicMembersCeptIs;

        /// <summary>
        /// list of all public properties to set
        /// </summary>
        private static PropertyInfo[] _allPublicMembers;

        /// <summary>
        /// the names of all the public members
        /// </summary>
        private static string[] _allPublicMemberNames;

        /// <summary>
        /// The result wrapped by this instance
        /// </summary>
        private GetRunStatusResult _rsr;

        /// <summary>
        /// Initializes a new instance of the <see cref="RunStatusResultWrapper"/> class.
        /// </summary>
        public RunStatusResultWrapper()
            :this(FakeResult)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RunStatusResultWrapper"/> class.
        /// </summary>
        /// <param name="result">The result to copy from.</param>
        public RunStatusResultWrapper(GetRunStatusResult result)
        {
            if (_allPublicMembers == null)
            {
                lock (SyncRoot)
                {
                    if (_allPublicMembers == null)
                    {
                        // get an array of all the public member names
                        var rsrtype = typeof(GetRunStatusResult);
                        _allPublicMembers =
                            rsrtype.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty)
                                .Where(p => p.CanWrite)
                                .ToArray();
                        _allPublicMembersCeptIs = _allPublicMembers
                                                 .Where(p => !p.Name.StartsWith("Is"))
                                                 .ToArray();
                        _allPublicMemberNames = _allPublicMembers.Select(p => p.Name).ToArray();
                    }
                }
            }

            SetEntireOriginalResult(result);
        }

        ~RunStatusResultWrapper()
        {
            if (_rsr != null)
                _rsr.PropertyChanged -= OnPropChanged;
        }

        /// <summary>
        /// Gets the raw original result
        /// </summary>
        public GetRunStatusResult OriginalResult
        {
            get
            {
                return _rsr;
            }
        }

        /// <summary>
        /// Resets all property values
        /// </summary>
        /// <param name="value">The new value</param>
        public void SetEntireOriginalResult(GetRunStatusResult value)
        {
            SetOriginalResult(value, _allPublicMembers);
        }


        /// <summary>
        /// Sets the test result without overwriting dashboard state values (Is* properties).
        /// This is done so that all parts of the code reference the exact same RunStatusResult instance
        /// </summary>
        /// <param name="value">The new test rules</param>
        public void SetOriginalResultFromDb(GetRunStatusResult value)
        {
            // logic for handling the IsExecuting flag
            if (IsExecuting)
            {
                // if we aren't already executing a new test and the testrunid changes, flag it as a new test
                if (!IsExecutingNewTest && value.TestRunId != TestRunId)
                    IsExecutingNewTest = true;

                // when we're executing a new test and the test is no longer pending, turn off the executing flag
                if (IsExecutingNewTest && !value.IsPending)
                {
                    IsExecuting = false;
                    IsExecutingNewTest = false;
                }
            }

            SetOriginalResult(value, _allPublicMembersCeptIs);
        }

        private void SetOriginalResult(GetRunStatusResult value, IEnumerable<PropertyInfo> members)
        {
            if (_rsr == null || _rsr == FakeResult)
            {
                if (value == null)
                    value = FakeResult;

                if (_rsr != value)
                {
                    if (_rsr != null)
                        _rsr.PropertyChanged -= OnPropChanged;
                    _rsr = value;
                    _rsr.PropertyChanged += OnPropChanged;
                    NotifyPropertyChanged(_allPublicMemberNames);
                }
            }
            else
            {
                foreach (var member in members)
                {
                    if (value == null)
                        member.SetValue(_rsr, null, null);
                    else
                        member.SetValue(_rsr, member.GetValue(value, null), null);
                }
            }
        }

        /// <summary>
        /// This region wraps all the default GetRunStatusResultFields
        /// </summary>
        #region GetRunStatusResultFields
        public DateTime? Duration
        {
            get { return OriginalResult.Duration; }
            set { OriginalResult.Duration = value; }
        }

        public bool? HasNotes
        {
            get { return OriginalResult.HasNotes; }
            set { OriginalResult.HasNotes = value; }
        }

        /// <summary>
        /// 0: No notes 1: Old notes 2: New notes
        /// </summary>
        public int HasNotesNewAndOld
        {
            get
            {
                int result = 0;

                foreach (TestNote note in TestNotes)
                {

                    if ((note.CreateDate - DateTime.Now).Days < 7)
                    {
                        result = 2;
                        break;
                    }
                    result = 1;
                }

                return result;
            }
        }

        public string LibraryName
        {
            get { return OriginalResult.LibraryName; }
            set { OriginalResult.LibraryName = value; }
        }

        public DateTime RunStartDate
        {
            get { return OriginalResult.RunStartDate; }
            set { OriginalResult.RunStartDate = value; }
        }

        public string RunStatus
        {
            get { return OriginalResult.RunStatus; }
            set { OriginalResult.RunStatus = value; }
        }
        public int RunStatusId
        {
            get { return OriginalResult.RunStatusId; }
            set { OriginalResult.RunStatusId = value; }
        }

        public byte RunStatusOrdinal
        {
            get { return OriginalResult.RunStatusOrdinal; }
            set { OriginalResult.RunStatusOrdinal = value; }
        }

        public string TestClassName
        {
            get { return OriginalResult.TestClassName; }
            set { OriginalResult.TestClassName = value; }
        }

        public int TestEnvironmentId
        {
            get { return OriginalResult.TestEnvironmentId; }
            set { OriginalResult.TestEnvironmentId = value; }
        }

        public int TestId
        {
            get { return OriginalResult.TestId; }
            set { OriginalResult.TestId = value; }
        }
        public string TestName
        {
            get { return OriginalResult.TestName; }
            set { OriginalResult.TestName = value; }
        }
        public string TestOwner
        {
            get { return OriginalResult.TestOwner; }
            set { OriginalResult.TestOwner = value; }
        }

        public int TestRunId
        {
            get { return OriginalResult.TestRunId; }
            set { OriginalResult.TestRunId = value; }
        }
        public bool IsChecked
        {
            get { return OriginalResult.IsChecked; }
            set { OriginalResult.IsChecked = value; }
        }
        public DateTime? CalculatedDuration
        {
            get { return OriginalResult.CalculatedDuration; }
            set { OriginalResult.CalculatedDuration = value; }
        }

        public bool IsExecuting
        {
            get { return OriginalResult.IsExecuting; }
            set { OriginalResult.IsExecuting = value; }
        }

        public bool IsExecutingNewTest
        {
            get { return OriginalResult.IsExecutingNewTest; }
            set { OriginalResult.IsExecutingNewTest = value; }
        }
        public string FullName
        {
            get { return OriginalResult.FullName; }
        }

        public string CompleteTestClassName
        {
            get { return OriginalResult.CompleteTestClassName; }
        }

        public ObservableCollection<TestNote> TestNotes
        {
            get { return OriginalResult.TestNotes; }
        }

        public LogEntries AllLogEntries
        {
            get { return OriginalResult.AllLogEntries; }
        }
        #endregion

        /// <summary>
        /// fired when a property on the original result is changed
        /// </summary>
        /// <param name="sender">the original result instance</param>
        /// <param name="e">the property changed arguments</param>
        private void OnPropChanged(object sender, PropertyChangedEventArgs e)
        {
            NotifyPropertyChanged(e.PropertyName);
        }

        /// <summary>
        /// TestRunData comparer class
        /// </summary>
        public class TestRunDataComparer : IEqualityComparer<RunStatusResultWrapper>
        {
            /// <summary>
            /// Determines whether the specified objects are equal.
            /// </summary>
            /// <returns>
            /// true if the specified objects are equal; otherwise, false.
            /// </returns>
            /// <param name="x">The first object of type TestRunData to compare.</param>
            /// <param name="y">The second object of type TestRunData to compare.</param>
            public bool Equals(RunStatusResultWrapper x, RunStatusResultWrapper y)
            {
                return x.FullName == y.FullName;
            }

            /// <summary>
            /// Returns a hash code for the specified object.
            /// </summary>
            /// <returns>
            /// A hash code for the specified object.
            /// </returns>
            /// <param name="obj">The <see cref="T:System.Object"/> for which a hash code is to be returned.</param>
            /// <exception cref="T:System.ArgumentNullException">The type of <paramref name="obj"/> is a reference type and <paramref name="obj"/> is null.</exception>
            public int GetHashCode(RunStatusResultWrapper obj)
            {
                return obj.FullName.GetHashCode();
            }
        }
    }

}
